﻿#region File Description
//PhysicsObject.cs
#endregion

#region Using Statements
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Squog_1._1.Managers;
#endregion

//REMOVE AFTER TESTING
using Microsoft.Xna.Framework.Input;

namespace Squog_1._1
{
    public abstract class PhysicsObject : DrawableObject
    {
        private Vector2 PrevPosition;
        private enum ObjectState { Ground, Air }
        private Vector2 Direction;
        private ObjectState objectState;
        private int Speed;

        private ObjectState CurrentObjectState 
        { 
            get { return objectState; } 
            set {
                if (value != objectState)
                    PrevObjectState = objectState; 
                objectState = value;
            }
        }

        private ObjectState PrevObjectState;

        private int Mass;
        public Vector2 Velocity;
        public Vector2 ControlVelocity;
        public virtual Rectangle collisionRect { get { return new Rectangle((int)Position.X, (int)Position.Y, (int)(spriteSheet.Width * Scale), (int)(spriteSheet.Height * Scale)); } }
        public bool IsStatic { get; set; }
        private bool IsJumping;
        public bool PhysicsOn;

        #region Teleport
        private bool IsTeleporting;
        private Vector2 TeleportDirection;
        private int TeleportTime;
        #endregion

        #region Speed
        public bool IsSpeed;
        private int PrevSpeed;
        #endregion

        private int JumpTime;

        /// <summary>
        /// Constructs a new Physics Object
        /// </summary>
        /// <param name="spriteSheet">The Sprite Sheet for the Object</param>
        /// <param name="Mass">The objects mass, over 3000 is considered infinite.</param>
        public PhysicsObject(SpriteSheet spriteSheet, int Mass)
            : base(spriteSheet)
        {
            this.Mass = Mass;
            this.IsStatic = false;
            IsTeleporting = false;
            TeleportDirection = new Vector2();
            Speed = 15;
        }

        public virtual void Reset(Vector2 StartingPosition)
        {
            this.objectState = ObjectState.Air;
            this.PrevPosition = StartingPosition;
            this.Position = StartingPosition;
            this.Velocity = Vector2.Zero;
            this.ControlVelocity = Vector2.Zero;
            Direction = new Vector2(1, 0);
            IsTeleporting = false;
            TeleportDirection = new Vector2();
            IsJumping = false;
            Speed = 15;
            PhysicsOn = true;
        }

        public virtual void PhysicsUpdate(GameTime gameTime)
        {
            if (PhysicsOn)
            {
                float time = ((float)gameTime.ElapsedGameTime.Milliseconds) / 50;

                //Dont general update is teleporting...
                if (!IsTeleporting)
                {
                    //General Update
                    JumpTime -= gameTime.ElapsedGameTime.Milliseconds;
                    if (JumpTime <= 0)
                    {
                        EndJump();
                    }

                    MovementUpdate(time);
                }

                if (IsTeleporting)
                {
                    TeleportTime -= gameTime.ElapsedGameTime.Milliseconds;
                    if (TeleportTime <= 0)
                    {
                        EndTeleport();
                    }
                }

                if (IsSpeed)
                {
                    SpeedUpdate();
                }
            }
        }

        public void MovementUpdate(float time)
        {
            if (IsJumping)
                Velocity.Y += -0.3f * time * JumpTime;

            if (CurrentObjectState == ObjectState.Air)
            {
                Position += (Velocity + ControlVelocity) * time + 0.5f * PhysicsManager.Gravity * time * time;
                Velocity += PhysicsManager.Gravity * time;
            }
            else
            {
                Position += ControlVelocity * time;
            }

            List<LineSegment> GroundCollisions = PhysicsManager.GroundCheck(collisionRect);
            if (GroundCollisions.Count != 0)
            {
                //There was a collision...
                bool TouchedGround = false;
                foreach (LineSegment Line in GroundCollisions)
                {
                    if (MyMath.IsOpposite(Velocity + ControlVelocity, -Line.NormalVector))// && MyMath.LeftOf(Line))
                    {
                        Vector2 PrevPoint = SelectPoint(Line);
                        if (MyMath.LeftOf(Line, PrevPoint + PrevPosition))
                        {
                            PopBack(Line, PrevPoint);

                            if (!(MyMath.AngleOf(Line) >= MathHelper.ToRadians(50) && MyMath.AngleOf(Line) <= MathHelper.ToRadians(310)))
                            {
                                TouchedGround = true;
                                Velocity.Y = 0;
                                if (CurrentObjectState != ObjectState.Ground)
                                    CurrentObjectState = ObjectState.Ground;
                                Direction = Line.Direction;
                                if (Keyboard.GetState().IsKeyDown(Keys.B))
                                    this.ToString();
                            }
                        }
                        else
                        {
                            //throw new Exception("Damn...");
                        }
                    }
                }

                if (!TouchedGround)
                {
                    CurrentObjectState = ObjectState.Air;
                    Direction = Vector2.UnitX;
                }

            }
            else
            {
                if (CurrentObjectState != ObjectState.Air)
                {
                    CurrentObjectState = ObjectState.Air;
                    Direction = Vector2.UnitX;
                }
            }

            PrevPosition = Position;
        }

        public void Move(Vector2 Direction)
        {
            ControlVelocity = MyMath.Projection(Direction, this.Direction) * Speed;
        }

        public void Jump()
        {
            if (objectState == ObjectState.Ground)
            {
                JumpTime = 150;
                IsJumping = true;
            }
        }

        public void EndJump()
        {
            IsJumping = false;
        }

        #region TeleportFunctions
        public void Teleport(Vector2 Direction)
        {
            TeleportDirection += Direction * 12;
        }

        public void EndTeleport()
        {
            if (!IsTeleporting)
            {
                TeleportDirection.X = 0;
                TeleportDirection.Y = 0;
                return;
            }

            IsTeleporting = false;

            TeleportDirection.Y = -1 * TeleportDirection.Y;

            if (TeleportDirection.Length() > 150)
            {
                TeleportDirection.Normalize();
                TeleportDirection *= 150;
            }

            Position += TeleportDirection;

            TeleportDirection.X = 0;
            TeleportDirection.Y = 0;
        }

        public void StartTeleport()
        {
            if (IsTeleporting)
                throw new Exception("Teleport Started again before EndTeleport was called.");
            IsTeleporting = true;
            TeleportDirection.X = 0;
            TeleportDirection.Y = 0;
            TeleportTime = 300;
        }
        #endregion

        #region SpeedFunctions
        public void SpeedUpdate()
        {
            //Check for speed connection spot
        }

        public void EndSpeed()
        {
            IsSpeed = false;
            Speed = PrevSpeed;            
        }

        public void StartSpeed()
        {
            if (IsSpeed)
                throw new Exception("Speed Started again before EndTeleport was called.");
            IsSpeed = true;
            PrevSpeed = Speed;
            Speed *= 2;

        }
        #endregion

        private Vector2 SelectPoint(LineSegment Line)
        {
            float theta = MyMath.AngleOf(Line);

            Vector2 Point = new Vector2();

            if (theta > MathHelper.PiOver2 * 3 || theta == 0)
            {
                //Choose Bottom Right Point
                Point.X += collisionRect.Width;
                Point.Y += collisionRect.Height;
            }
            else if (theta > MathHelper.Pi)
            {
                //Choose Top Right Point
                Point.X += collisionRect.Width;
            }
            else if (theta > MathHelper.PiOver2)
            {
                //Choose Top Left Point
            }
            else
            {
                //Choose Bottom Left Point
                Point.Y += collisionRect.Height;
            }

            return Point;
        }

        public void PopBack(LineSegment Line, Vector2 PrevPoint)
        {
            if (Line.point1.X == Line.point2.X)
            {
                //If the line is vertical
                if (Line.point1.X > PrevPosition.X)
                    Position = new Vector2(Line.point1.X - collisionRect.Width, Position.Y);
                else
                    Position = new Vector2(Line.point1.X, Position.Y);
            } else if (Line.point1.Y == Line.point2.Y)
            {
                //If the line is horizontal
                if (Line.point1.Y > PrevPosition.Y)
                    Position = new Vector2(Position.X, Line.point1.Y - collisionRect.Height);
                else
                    Position = new Vector2(Position.X, Line.point1.Y);
            }
            else if (MyMath.AngleOf(Line) >= MathHelper.ToRadians(60) && MyMath.AngleOf(Line) <= MathHelper.ToRadians(300))
            {
                //Too steep to climb
                //TODO: Popback along the normal
            }
            else
            {
                //Climbable slope
                Vector2 Displacement = Position - PrevPosition;

                Vector2 C = MyMath.CollisionPoint(PrevPosition + PrevPoint, Displacement, Line);

                Vector2 NewPosition = C - PrevPoint;
                NewPosition += (Position - NewPosition).Length() * Line.Direction;
                Position = NewPosition;
            }
        }
    }
}
